// 
// A Cache class.
//
//

#ifndef __CACHE_H__
#define __CACHE_H__

#include "CPU.h"
#include "base/Entity.h"
#include "base/LoadRequest.h"
#include "base/Packet.h"
#include "base/QuiesceRequest.h"
#include "base/QuiesceResponse.h"
#include "base/RawFlushRequest.h"
#include "base/Response.h"
#include "base/SnoopResponse.h"
#include "base/StoreRequest.h"
#include "mem/CacheHash.h"
#include "mem/InvalidateRequest.h"
#include "mem/MSHR.h"
#include "mem/UpdateRequest.h"
#include <map>

typedef struct {
    Time alarm;
    Packet* request;
} PacketDelay;

/// A simple non-blocking parameterizable cache with no coherence
class Cache : public Entity {
  public:
    NEW_DELETE_OPERATORS_H(Cache,Entity);

    Cache (const char* name,
           uint size,
           uint blockSize,
           uint assoc,
           uint delay,
           uint vcsize,
           uint mshrs,
           LineFlags const* protoFlags,
           Counter* statAccesses,
           Counter* statHits,
           Counter* statMisses,
           Bucket* _bktStall,
           Bucket* _bktUnavailable);
    virtual ~Cache ();

    virtual void tick ();

    /// \todo This should not be public
    void decOutstandingEvicts ();

    /// Route the packet as if it was sent from the cache \todo should be deprecated
    virtual Result routeExternal (Packet* r);

    uint getBlockSize () { return blockSize; }

  protected:

    /** Retrieve a list of matching cache lines.

        Returns a list CacheEntry*'s whose address matches the block
        address of the MemoryRequest.
    */
    virtual List<CacheEntry*>* retrieve (MemoryRequest*);

    Logger* getLogger () { return cacheLog; }

    /// cancel and delete all oustanding requests
    virtual void cancelRequests ();

    /// resend blocked requests that were previously blocked
    virtual void flushBlockedRequests ();
    
    /// Called when a refill comes back, but before any action has taken place
    virtual void hookPreLoadRefill (LoadRequest* lr, MSHR* mshr);

    /** Called right before refill is complete, to set cache flags, etc.

        mshr->victimEntry will be added to the cache when this returns.
    */  
    virtual void hookLoadRefill (LoadRequest* lr, MSHR* mshr);

    /** Called when the cache has received a RawFlushRequest and is ready
        to do the flush.  

        This should perform the actual flush of the cache, which
        probably involves calling some variant of
        hash->Cache::visitRawFlush
    */
    virtual void hookRawFlush (RawFlushRequest* rfr) = 0;

    /** Called when the cache has received an UpdateRequest and is ready
        to do the update.  

        This should perform the actual update of the cache, which
        probably involves calling some variant of
        hash->Cache::visitUpdate
    */    
    virtual void hookUpdate (UpdateRequest* ir);

    /// Called when the cache receives a response to valid outstanding Request
    virtual void hookResponse (Response* resp);

    /// returns true if the cache is stalled waiting on some request
    bool stalled (Packet* r);
    
    uint            size;
    uint            blockSize;
    uint            assoc;
    uint            delay;
    uint            maxMSHRs;
    uint            reservedVCs;

    Logger*         cacheLog;

    CacheHash*      hash;


    LineFlags const * const protoFlags;

    uint            lg2BlockSize;
    uint            lg2Assoc;
    uint            indexShift;
    uint            indexMask;
    uint            tagShift;
    bool            quiesce;
    QuiesceRequest* quiesceRequest;

    Time            lastAccessClock;

    List<PacketDelay*>* agingDataRequests;

    // limited to maxMSHRs
    List<MSHR*>*   mshrs;

    List<LoadRequest*>* canceledLoadRequests;
    
    /** flushes the CacheEntry to memory if necessarry
        \param rfr a RawFlushRequest

        If CacheEntry is dirty and within the bounds of the
        RawFlushRequest, its contents are copied directly to actual
        memory.  Always returns true.
    */
    static bool     visitRawFlush (VA addr, CacheEntry*& e, void* rfr);

    /** Invalidates an individual CacheEntry
        \param ir an InvalidationRequest

        Returns false if the CacheEntry in within bound of the
        InvalidationRequest, which will cause the visit function to
        remove the entry from the cache.
     */
    static bool     visitInvalidate (VA addr, CacheEntry*& e, void* ir);

    /** Verify's the CacheEntry's data matches actual memory.
        \param vr a VerifyRequest

        Sets the vr->failed to true if CacheEntry's data is different
        than actual memory. Always returns true
    */
    static bool     visitVerify (VA addr, CacheEntry*& e, void* vr);

    /// Updates the CacheEntry with the data from actual memory.
    static bool     visitUpdate (VA addr, CacheEntry*& e, void*);

    Result          handle_MemoryRequest (Packet* r);
    Result          handle_Response (Packet* r);
    Result          handle_CancelRequestsRequest (Packet* r);
    virtual Result  handle_RawFlushRequest (Packet* r);
    Result          handle_DataRequest (Packet* r);
    Result          handle_InvalidateRequest (Packet* r);
    Result          handle_VerifyRequest (Packet* r);
    Result          handle_UpdateRequest (Packet* r);
    Result          handle_QuiesceRequest (Packet* r);
    virtual Result  handle_QuiesceResponse (Packet* r);
    Result          handleDuplicateQuiesceRequest (Packet* r);
    virtual Result  handle_RawMemoryRequest (Packet* r);

    bool isSharedCache ();
    Response* makeResponse (DataRequest* r);

    uint wordOffset (VA addr) const { return (ADDR_UINT32(addr) & (blockSize - 1)) >> LG2_WORD_SIZE; }
    VA blockAddr (VA addr) const { return ADDR_UINT32(addr) & ~(VA)(blockSize - 1); }
    bool blockMatch (VA lhs, VA rhs) const { return blockAddr(lhs) == blockAddr(rhs); }
    bool setMatch (VA lhs, VA rhs) const { return hash->hash(lhs) == hash->hash(rhs); }
    
    virtual void          invalidate (InvalidateRequest* ir);
    virtual void          invalidateDataRequests (InvalidateRequest* ir);

    virtual Result        handleAccess (DataRequest* dr, List<CacheEntry*>* entries) = 0;
    virtual Result        handleStoreHit (StoreRequest* sr, CacheEntry* entry);
    virtual Result        handleLoadHit (LoadRequest* sr, CacheEntry* entry);

    virtual Result        handleMiss (DataRequest* dr, CacheEntry* ve);
    virtual LoadRequest*  makeRefillRequest (DataRequest* dr);
    virtual bool          canMiss (VA addr) const;
    virtual bool          isAssocSetFull (VA addr);
    virtual MSHR*         createMSHR (DataRequest* dr, CacheEntry* ve);
    virtual void          routeOrEnqueueNewRefillRequest (DataRequest* dr, CacheEntry* ve);
    virtual Result        handleMissNoevict (DataRequest* dr, CacheEntry* ve);
    virtual Result        handleMissEvict (DataRequest* dr,
                                           CacheEntry* ve,
                                           StoreRequest* dataWB);

    virtual CacheEntry*   assignVictimLine (MemoryRequest const* r);
    virtual CacheEntry*   getBestVictim (List<CacheEntry*> const* const list);

    virtual void          routeOrEnqueue (DataRequest* dr);
    virtual void          enqueueStalledRequest (DataRequest* dr);
    virtual void          enqueueBlockedRequest (DataRequest* dr);
    bool                  isReadyForQuiesce ();
    void                  handleNoFetchAtWriteMiss (DataRequest* dr, CacheEntry* ve);
    bool                  isForNoFetchAtWriteMiss (DataRequest* dr);
    void                  sendQuiesceRequest (QuiesceRequest* qr);

    // The following variables constitute the output buffer.  Caches
    // that have an input delay (all except L1) buffer their inputs
    // instead of stalling them, so there should not be any stalled
    // DataRequest except in the last cache.  Blocked loads are those
    // that were nack-ed with a RESPONSE_NACK_BLOCK.  They are flushed
    // when the transaction on their destination processor commits or
    // aborts.
    DataRequest*          stalledLoad;
    DataRequest*          blockedLoad;
    Time                  blockedLoadTime;
    int                   blockedTimeouts;
    DataRequest*          stalledEvict;
    List<SnoopResponse*>* stalledSnoopResponse; 
    List<Response*>*      pendingResponse; 
    
    int  outstandingEvicts;

    Counter*      statAccesses;
    Counter*      statHits;
    Counter*      statMisses;
    Bucket*       bktStall;
    Bucket*       bktUnavailable;

    Time   lastRawFlushTime;
    VA     lastRawFlushStart;
    size_t lastRawFlushLength;
private:
    uint                  numVictimsInSet (VA const addr) const;
    bool                  isInMSHR (VA const addr) const;
    
//  something to do with the victimCache;
};
#endif // __CACHE_H__
